﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Noris.Tools.FrxEditor.Data
{
	/// <summary>
	/// Třída podporující práci na pozadí (Background)
	/// </summary>
    public class WorkingThread
    {
        #region Singleton
		private WorkingThread()
        {
            this._WaitForRequest = new AutoResetEvent(false);
            this._WorkThread = new Thread(this._BackgroundThreadRun);
            this._WorkThread.IsBackground = true;
            this._WorkThread.Name = "WorkingThread";
            this._WorkItems = new Queue<WorkItemData>();
        }
        private static WorkingThread Current
        {
            get
            {
                if (_Instance == null)
                {
                    lock (_Locker)
                    {
                        if (_Instance == null)
                        {
                            WorkingThread instance = new WorkingThread();
                            instance._WorkThread.Start();
                            _Instance = instance;
                        }
                    }
                }
                return _Instance;
            }
        }
        private static object _Locker = new object();
        private static WorkingThread _Instance;
        private Thread _WorkThread;
        #endregion
        #region Public static metody
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRun"></param>
		/// <param name="actionDone"></param>
        public static void AddItem(Action actionRun, Action actionDone)
        {
            Current._AddItem(new WorkItemData(actionRun, null, null, null, actionDone, null, null));
        }
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRun"></param>
		/// <param name="actionError"></param>
		/// <param name="actionDone"></param>
		public static void AddItem(Action actionRun, Action<Exception> actionError, Action actionDone)
        {
            Current._AddItem(new WorkItemData(actionRun, null, null, actionError, actionDone, null, null));
        }
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRun"></param>
		/// <param name="actionProgress"></param>
		/// <param name="actionDone"></param>
		public static void AddItem(Action actionRun, Action<WorkingThreadProgressArgs> actionProgress, Action actionDone)
		{
			Current._AddItem(new WorkItemData(actionRun, null, actionProgress, null, actionDone, null, null));
		}
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRun"></param>
		/// <param name="actionProgress"></param>
		/// <param name="actionError"></param>
		/// <param name="actionDone"></param>
		public static void AddItem(Action actionRun, Action<WorkingThreadProgressArgs> actionProgress, Action<Exception> actionError, Action actionDone)
		{
			Current._AddItem(new WorkItemData(actionRun, null, actionProgress, actionError, actionDone, null, null));
		}
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRunParam"></param>
		/// <param name="data"></param>
		/// <param name="actionDone"></param>
		public static void AddItem(Action<WorkingThreadArgs> actionRunParam, object data, Action actionDone)
		{
			Current._AddItem(new WorkItemData(null, actionRunParam, null, null, actionDone, null, null));
		}
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRunParam"></param>
		/// <param name="data"></param>
		/// <param name="actionError"></param>
		/// <param name="actionDone"></param>
		public static void AddItem(Action<WorkingThreadArgs> actionRunParam, object data, Action<Exception> actionError, Action actionDone)
		{
			Current._AddItem(new WorkItemData(null, actionRunParam, null, actionError, actionDone, null, data));
		}
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRunParam"></param>
		/// <param name="data"></param>
		/// <param name="actionProgress"></param>
		/// <param name="actionDone"></param>
		public static void AddItem(Action<WorkingThreadArgs> actionRunParam, object data, Action<WorkingThreadProgressArgs> actionProgress, Action actionDone)
		{
			Current._AddItem(new WorkItemData(null, actionRunParam, actionProgress, null, actionDone, null, null));
		}
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRunParam"></param>
		/// <param name="data"></param>
		/// <param name="actionProgress"></param>
		/// <param name="actionError"></param>
		/// <param name="actionDone"></param>
		public static void AddItem(Action<WorkingThreadArgs> actionRunParam, object data, Action<WorkingThreadProgressArgs> actionProgress, Action<Exception> actionError, Action actionDone)
		{
			Current._AddItem(new WorkItemData(null, actionRunParam, actionProgress, actionError, actionDone, null, data));
		}
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRunParam"></param>
		/// <param name="data"></param>
		/// <param name="actionDoneParam"></param>
		public static void AddItem(Action<WorkingThreadArgs> actionRunParam, object data, Action<WorkingThreadDoneArgs> actionDoneParam)
        {
			Current._AddItem(new WorkItemData(null, actionRunParam, null, null, null, actionDoneParam, data));
        }
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRunParam"></param>
		/// <param name="data"></param>
		/// <param name="actionError"></param>
		/// <param name="actionDoneParam"></param>
		public static void AddItem(Action<WorkingThreadArgs> actionRunParam, object data, Action<Exception> actionError, Action<WorkingThreadDoneArgs> actionDoneParam)
        {
            Current._AddItem(new WorkItemData(null, actionRunParam, null, actionError, null, actionDoneParam, null));
        }
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRunParam"></param>
		/// <param name="data"></param>
		/// <param name="actionProgress"></param>
		/// <param name="actionDoneParam"></param>
		public static void AddItem(Action<WorkingThreadArgs> actionRunParam, object data, Action<WorkingThreadProgressArgs> actionProgress, Action<WorkingThreadDoneArgs> actionDoneParam)
        {
            Current._AddItem(new WorkItemData(null, actionRunParam, actionProgress, null, null, actionDoneParam, data));
        }
		/// <summary>
		/// Přidá další požadavek na akci na pozadí
		/// </summary>
		/// <param name="actionRunParam"></param>
		/// <param name="data"></param>
		/// <param name="actionProgress"></param>
		/// <param name="actionError"></param>
		/// <param name="actionDoneParam"></param>
		public static void AddItem(Action<WorkingThreadArgs> actionRunParam, object data, Action<WorkingThreadProgressArgs> actionProgress, Action<Exception> actionError, Action<WorkingThreadDoneArgs> actionDoneParam)
        {
            Current._AddItem(new WorkItemData(null, actionRunParam, actionProgress, actionError, null, actionDoneParam, data));
        }
		/// <summary>
		/// Tuto metodu volá výkonná metoda běžící na pozadí.
		/// Řídící vrstva WorkingThread zajistí předání dat (progress) do té metody, která byla zadána při definování úkolu.
		/// </summary>
		/// <param name="progress"></param>
		public static void DoProgress(ProgressData progress)
		{
			Current._DoProgress(progress);
		}
		/// <summary>
		/// Ukončí výkon celého systému Background.
		/// Volá se při ukončení aplikace.
		/// </summary>
        public static void Close()
        {
            Current._Close();
        }
        #endregion
        #region Private instanční metody
        private void _AddItem(WorkItemData item)
        {
            item.CallerThread = Thread.CurrentThread;      // Do požadavku si uložím thread, který jej vyvolal.
            lock (this._WorkItems)
            {
                this._WorkItems.Enqueue(item);
            }
            this._WaitForRequest.Set();        // Zazvoníme na zvoneček, že přišla nová položka práce
        }
		private void _DoProgress(ProgressData progress)
		{
			WorkItemData workItem = this._CurrentItem;
			if (workItem != null)
				workItem.DoProgress(progress);
		}
        private void _Close()
        {
            lock (this._WorkItems)
            {
                this._WorkItems.Clear();
            }
            this._WorkThread.Abort();
        }
        #endregion
        #region Thread on background
        /// <summary>
        /// V této metodě stráví thread na pozadí svůj aktivní život (když se něco děje).
        /// </summary>
        private void _BackgroundThreadRun()
        {
            while (true)
            {
                WorkItemData workItem = this._BackgroundGetNext();
                if (workItem != null)
                {
                    try
                    {
						this._CurrentItem = workItem;
                        workItem.Run();
                    }
                    catch (Exception exc)
                    {
                        workItem.Exc = exc;
                    }
                    finally
                    {
                        workItem.Done();
						this._CurrentItem = null;
                    }
                }
            }
        }
		/// <summary>
		/// Aktuálně běžící aktivita na pozadí, vždy je jen jedna (tento objekt nepodporuje pravý multithreading).
		/// Je nastaveno před spuštěním akce Run, a je nullováno po dokončení akce Done.
		/// </summary>
		private WorkItemData _CurrentItem;
        /// <summary>
        /// V této metodě stráví thread na pozadí svůj pasivní život (když se čeká na to, až se bude něco dít).
        /// </summary>
        /// <returns></returns>
        private WorkItemData _BackgroundGetNext()
        {
            while (true)
            {
                this._WaitForRequest.Reset();          // Pokud někdo zvonil na zvoneček, tak zvoneček utišíme.
                lock (this._WorkItems)
                {
                    if (this._WorkItems.Count > 0)
                    {
                        WorkItemData item = this._WorkItems.Dequeue();
                        return item;
                    }
                }
                this._WaitForRequest.WaitOne();        // Aktivujeme zvoneček a jdeme spát, dokud nám nezazvoní.
            }
        }
        /// <summary>
        /// Zvoneček = signalizační zařízení, které umožní vláknu na pozadí "spát", když nemá co dělat.
        /// </summary>
        private AutoResetEvent _WaitForRequest;
        /// <summary>
        /// Seznam položek práce
        /// </summary>
        private Queue<WorkItemData> _WorkItems;
        #endregion
        #region class WorkItemData : soukromá položka práce na pozadí
		/// <summary>
		/// WorkItemData : soukromá položka práce na pozadí.
		/// Obsahuje zachycené parametry definující jednotku práce na pozadí = jednu akci.
		/// </summary>
        protected class WorkItemData
        {
			internal WorkItemData(Action actionRun, Action<WorkingThreadArgs> actionRunParam, Action<WorkingThreadProgressArgs> actionProgress, Action<Exception> actionError, Action actionDone, Action<WorkingThreadDoneArgs> actionDoneParam, object data)
            {
                this.ActionRun = actionRun;
                this.ActionRunParam = actionRunParam;
                this.ActionProgress = actionProgress;
                this.ActionError = actionError;
                this.ActionDone = actionDone;
                this.ActionDoneParam = actionDoneParam;
                this.Data = data;
            }
            /// <summary>
            /// Thread volajícího.
			/// Třeba jednou budeme umět invokovat do tohoto threadu událost Done.
            /// </summary>
			internal Thread CallerThread;
			/// <summary>
			/// Akce Run (bezparametrická) = spouští se, když na tuto akci přijde řada ve frontě
			/// </summary>
            internal Action ActionRun;
			/// <summary>
			/// Akce Run (s parametrem) = spouští se, když na tuto akci přijde řada ve frontě
			/// </summary>
			internal Action<WorkingThreadArgs> ActionRunParam;
			/// <summary>
			/// Akce, která zobrazuje uživateli progres v metodě na pozadí
			/// </summary>
            internal Action<WorkingThreadProgressArgs> ActionProgress;
			/// <summary>
			/// Akce, která je vyvolána v případě chyby, která se stala ve výkonné metodě.
			/// </summary>
            internal Action<Exception> ActionError;
			/// <summary>
			/// Akce Done (bezparametrická) = spouští se, když doběhne výkonná metoda na pozadí
			/// </summary>
            internal Action ActionDone;
			/// <summary>
			/// Akce Done (s parametrem) = spouští se, když doběhne výkonná metoda na pozadí
			/// </summary>
			internal Action<WorkingThreadDoneArgs> ActionDoneParam;
			/// <summary>
			/// Úložiště objektu s daty, který se předává do výkonné metody ActionRun (metoda s parametrem)
			/// </summary>
            internal object Data;
			/// <summary>
			/// Zachycená výjimka
			/// </summary>
            internal Exception Exc;
			/// <summary>
			/// Vstupní bod výkonné metody.
			/// Spouští se v threadu na pozadí.
			/// Řeší se zde volba akce ActionRun() / ActionRunParam(Data)
			/// </summary>
            internal void Run()
            {
                if (this.ActionRun != null)
                    this.ActionRun();
                else if (this.ActionRunParam != null)
                    this.ActionRunParam(new WorkingThreadArgs(this.Data));
            }
			/// <summary>
			/// Vstupní bod progresu.
			/// </summary>
			/// <param name="progress"></param>
			internal void DoProgress(ProgressData progress)
			{
				if (this.ActionProgress != null)
					this.ActionProgress(new WorkingThreadProgressArgs(this.Data, progress));
			}
			/// <summary>
			/// Vstupní bod pro volání metody Done, po doběhnutí metody Run.
			/// </summary>
			internal void Done()
            {
                if (this.Exc != null && this.ActionError != null)
                    this.ActionError(this.Exc);
                else if (this.ActionDone != null)
                    this.ActionDone();
                else if (this.ActionDoneParam != null)
                    this.ActionDoneParam(new WorkingThreadDoneArgs(this.Data, this.Exc));
            }
		}
        #endregion
	}
	#region Třídy argumentů
	/// <summary>
	/// Třída argumentů pro předání dat o progresu
	/// </summary>
	public class WorkingThreadProgressArgs : WorkingThreadArgs
    {
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="data"></param>
		/// <param name="progressData"></param>
		public WorkingThreadProgressArgs(object data, ProgressData progressData)
            : base(data)
        {
			this.ProgressData = progressData;
		}
		/// <summary>
		/// Data o postupu
		/// </summary>
		public ProgressData ProgressData { get; private set; }
    }
	/// <summary>
	/// Třída argumentů pro převzetí výsledku po dokončení běhu metody na pozadí
	/// </summary>
    public class WorkingThreadDoneArgs : WorkingThreadArgs
    {
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="data"></param>
		/// <param name="exc"></param>
        public WorkingThreadDoneArgs(object data, Exception exc)
            : base(data)
        {
            this.Exc = exc;
        }
		/// <summary>
		/// Chyba, k níž došlo
		/// </summary>
        public Exception Exc { get; private set; }
    }
	/// <summary>
	/// Základní třída argumentů pro metody pracující na pozadí, obsahuje objekt s daty
	/// </summary>
    public class WorkingThreadArgs
    {
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="data"></param>
        public WorkingThreadArgs(object data)
        {
            this.Data = data;
        }
		/// <summary>
		/// Uživatelská data
		/// </summary>
        public object Data { get; private set; }
    }
	#endregion
	#region class ProgressData : Data do/z progress baru.
	/// <summary>
	/// ProgressData : Data do/z progress baru.
	/// Default hodnota každé datové property je null, což při setování má význam "Nesetovat odpovídající property".
	/// </summary>
	public class ProgressData
	{
		/// <summary>Má být viditelný text Caption = před progress barem?  Null hodnota (default) = neměnit aktuální stav</summary>
		public bool? ProgressCaptionVisible { get; set; }
		/// <summary>Text do políčka Caption.  Null hodnota (default) = neměnit aktuální stav</summary>
		public string ProgressCaptionText { get; set; }
		/// <summary>Má být viditelný Progress bar?  Null hodnota (default) = neměnit aktuální stav</summary>
		public bool? ProgressBarVisible { get; set; }
		/// <summary>Hodnota do ProgressBaru, v rozmezí 0 až 1.  Null hodnota (default) = neměnit aktuální stav</summary>
		public float? ProgressBarRatio { get; set; }
		/// <summary>Má být viditelný text Count = za progress barem? Text je určen pro zobrazení počtu.  Null hodnota (default) = neměnit aktuální stav</summary>
		public bool? ProgressCountVisible { get; set; }
		/// <summary>Text do políčka Count. Text je určen pro zobrazení počtu.  Null hodnota (default) = neměnit aktuální stav</summary>
		public string ProgressCountText { get; set; }
		/// <summary>Má být viditelný text Error = za textem Count? Text je určen pro zobrazení chyb.  Null hodnota (default) = neměnit aktuální stav</summary>
		public bool? ProgressErrorVisible { get; set; }
		/// <summary>Text do políčka Error. Text je určen pro zobrazení chyb.  Null hodnota (default) = neměnit aktuální stav</summary>
		public string ProgressErrorText { get; set; }
	}
	#endregion
}
